Skip to content

Conversation

@zomp
Copy link

@zomp zomp commented Dec 18, 2025

Summary by CodeRabbit

  • New Features

    • Branch management and merge commands; project branch support via CLI option, config, or env; branch-aware pull/push/sync/tag flows and messages.
  • Validation

    • Config branch value validated and normalized (non-empty, trimmed).
  • UX

    • Branch appended to loading/status text; clearer "not found" (404) error messages.
  • Documentation

    • Clarified guidance for passing CLI arguments.
  • Tests

    • New fixture, unit, and extensive e2e branching/merge tests.
  • Chores

    • Removed explicit pre/post-scripts enablement from NPM settings.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

Walkthrough

Add optional branch support across schema, config, CLI options, commands, utilities, and tests; introduce branch and merge CLI commands; propagate branch context into API calls and messages; add HTTP 404 handling in errorFromLoadable; minor .npmrc and HACKING.md tweaks.

Changes

Cohort / File(s) Summary
Schema & Types
\schema.json`, `src/schema.d.ts``
Add root branch property (string, minLength:1) and branch?: string to TS Schema interface.
CLI Core & Options
\src/cli.ts`, `src/options.ts``
New PROJECT_BRANCH option (-b, --branch <name>, env TOLGEE_BRANCH); BaseOptions gains branch?: string; CLI defaults branch from config; register branch and merge commands.
Config Parsing & Validation
\src/config/tolgeerc.ts``
Use node: built-ins for imports; validate and trim branch in parseConfig (must be non-empty string).
Branch Commands
\src/commands/branch.ts`, `src/commands/merge.ts``
Add new branch command (list/create/delete with origin resolution) and merge command (preview/apply merges, detect conflicts, show merge URL).
Command Branch Integration
\src/commands/pull.ts`, `src/commands/push.ts`, `src/commands/tag.ts`, `src/commands/sync/*.ts`, `src/commands/sync/compare.ts``
Thread branch into API requests and import/export params; use appendBranch for branch-aware loading/status messages.
Branch Utilities
\src/utils/branch.ts``
New exports: appendBranch(branch?), fetchBranches(cmd), listBranches(branches).
Client Error Handling
\src/client/errorFromLoadable.ts``
Add explicit HTTP 404 handling returning "Requested data not found" with details.
Tests & Fixtures
\test/fixtures/validTolgeeRc/withBranch.json`, `test/unit/config.test.ts`, `test/e2e/*`, `test/e2e/utils/api/common.ts`, `test/e2e/utils/api/tags.ts``
Add fixture and unit test for branch; many e2e tests for branch/merge/pull/sync/tags; add helpers createBranch, createKey; update expectations and scopes; note duplicated Branching suites in some tests.
Docs & Config Files
\.npmrc`, `HACKING.md``
Remove enable-pre-post-scripts=true from .npmrc; minor wording tweak in HACKING.md.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI
  participant Config as "Local Config"
  participant Command as "branch/merge/pull/push/sync"
  participant API as "Tolgee API"
  participant FS as "Local FS / Extractor"

  Note over CLI,Config: User runs CLI (may pass -b/--branch) or rely on config.branch
  User->>CLI: invoke command (with optional --branch)
  CLI->>Config: load config (reads config.branch)
  CLI->>Command: start command (opts.branch resolved)
  Command->>API: request (includes branch in query/body)
  API-->>Command: response
  alt Fetch & extract flow
    Command->>FS: extract/unzip (log includes appendBranch)
    FS-->>Command: done
  end
  Command-->>CLI: complete (messages include appendBranch)
  CLI-->>User: output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • JanCizmar

Poem

🐇
I found a branch and tucked it neat,
Into CLI commands and tests to greet.
Push, pull, merge — they know the name,
Tiny rabbit hopped and fixed the frame.
Joyful hops for one small change 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: add branching' directly and clearly summarizes the main objective of the changeset, which introduces comprehensive branching support across the CLI.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4cfed9d and 606f9fc.

📒 Files selected for processing (3)
  • src/commands/sync/compare.ts
  • src/commands/sync/sync.ts
  • src/commands/tag.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/commands/tag.ts (1)
src/utils/branch.ts (1)
  • appendBranch (6-8)
src/commands/sync/compare.ts (2)
src/utils/logger.ts (1)
  • loading (125-155)
src/utils/branch.ts (1)
  • appendBranch (6-8)
src/commands/sync/sync.ts (3)
src/client/TolgeeClient.ts (1)
  • TolgeeClient (34-34)
src/utils/logger.ts (1)
  • loading (125-155)
src/utils/branch.ts (1)
  • appendBranch (6-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: E2E Tests (ubuntu-latest, 18)
  • GitHub Check: Unit Tests (windows-latest, 22)
  • GitHub Check: E2E Tests (ubuntu-latest, 22)
  • GitHub Check: E2E Tests (ubuntu-latest, 20)
🔇 Additional comments (6)
src/commands/tag.ts (1)

47-65: LGTM! Branch support correctly integrated.

The branch parameter is properly threaded through the tagging operation:

  • Loading message reflects the branch context via appendBranch
  • API call conditionally includes branch as a query parameter
  • Implementation is clean and consistent
src/commands/sync/compare.ts (1)

29-37: LGTM! Branch awareness correctly implemented.

The compare flow now supports branch-specific key fetching with appropriate user feedback.

src/commands/sync/sync.ts (4)

33-48: LGTM! Backup function correctly extended for branch support.

The function signature change is minimal and the branch is properly passed to the export operation via filterBranch.


96-104: LGTM! Branch support correctly added to key fetching.


145-148: LGTM! Backup invocation correctly passes branch.


166-175: LGTM! Key creation correctly includes branch context.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/utils/branch.ts (1)

1-3: Consider clarifying the no-branch message.

The message " (no or default branch)" is ambiguous—it's unclear whether the system is using a default branch or operating without any branch context. Consider making the message more explicit about the actual behavior.

💡 Possible alternative wording

If no branch means operating on the default/main branch:

-  return branch ? ` (branch "${branch}")` : ' (no or default branch)';
+  return branch ? ` (branch "${branch}")` : ' (default branch)';

Or if it means no branch filtering is applied:

-  return branch ? ` (branch "${branch}")` : ' (no or default branch)';
+  return branch ? ` (branch "${branch}")` : ' (all branches)';

Choose based on the actual Tolgee API behavior when no branch is specified.

src/commands/push.ts (1)

247-247: Consider adjusting the message when no branch is specified.

When opts.branch is undefined, appendBranch returns ' (no or default branch)', which will display as "Importing (no or default branch)...". This might be confusing for users who don't use branching at all. Consider showing the branch suffix only when a branch is explicitly specified:

🔎 Suggested change to appendBranch utility
// In src/utils/branch.ts
 export function appendBranch(branch?: string) {
-  return branch ? ` (branch "${branch}")` : ' (no or default branch)';
+  return branch ? ` (branch "${branch}")` : '';
 }

Alternatively, if the current behavior is intentional to always indicate branch status, the message could be rephrased to ' (default branch)' for clarity.

Also applies to: 263-263

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f7cffb and 068ac75.

📒 Files selected for processing (12)
  • .npmrc (0 hunks)
  • HACKING.md (1 hunks)
  • schema.json (1 hunks)
  • src/cli.ts (2 hunks)
  • src/commands/pull.ts (3 hunks)
  • src/commands/push.ts (4 hunks)
  • src/config/tolgeerc.ts (2 hunks)
  • src/options.ts (2 hunks)
  • src/schema.d.ts (1 hunks)
  • src/utils/branch.ts (1 hunks)
  • test/__fixtures__/validTolgeeRc/withBranch.json (1 hunks)
  • test/unit/config.test.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • .npmrc
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-13T09:27:51.189Z
Learnt from: stepan662
Repo: tolgee/tolgee-cli PR: 185
File: src/schema.d.ts:150-152
Timestamp: 2025-08-13T09:27:51.189Z
Learning: In the Tolgee CLI codebase, schema validation uses JSON schema from schema.json as the source of truth. The src/schema.d.ts file contains automatically generated TypeScript types derived from the JSON schema for compile-time type checking only, not runtime exports. CLI validation happens through JSON schema validation, not through runtime TypeScript enums.

Applied to files:

  • src/schema.d.ts
  • src/config/tolgeerc.ts
🧬 Code graph analysis (4)
test/unit/config.test.ts (1)
src/config/tolgeerc.ts (1)
  • loadTolgeeRc (102-133)
src/cli.ts (1)
src/options.ts (1)
  • PROJECT_BRANCH (59-62)
src/commands/pull.ts (4)
src/utils/branch.ts (1)
  • appendBranch (1-3)
src/utils/prepareDir.ts (1)
  • prepareDir (4-15)
src/utils/logger.ts (1)
  • loading (125-155)
src/utils/zip.ts (1)
  • unzipBuffer (32-45)
src/commands/push.ts (1)
src/utils/branch.ts (1)
  • appendBranch (1-3)
🔇 Additional comments (16)
HACKING.md (1)

17-18: LGTM: Grammar improvement.

The addition of "to" improves the grammatical clarity of the sentence.

src/schema.d.ts (1)

83-86: LGTM: Type definition aligns with schema.

The optional branch field is correctly defined and consistent with the schema.json update in this PR.

test/__fixtures__/validTolgeeRc/withBranch.json (1)

1-5: LGTM: Valid test fixture.

The fixture appropriately tests configuration loading with a branch value.

test/unit/config.test.ts (1)

97-105: LGTM: Test case properly validates branch loading.

The test follows established patterns and correctly verifies that branch values are loaded from configuration files.

src/commands/pull.ts (3)

13-13: LGTM: Proper import of branch utility.


45-45: LGTM: Branch filtering correctly integrated.

The branch value is properly passed to the export API call via filterBranch, enabling branch-aware data fetching.


65-72: LGTM: User messages enhanced with branch context.

Both fetch and extract operations now provide clear branch context to users through the appendBranch utility.

src/cli.ts (2)

22-22: LGTM: Proper import of branch option.


184-184: LGTM: Branch option correctly wired with config default.

The PROJECT_BRANCH option follows the established pattern for other global options, properly defaulting to the value from configuration.

schema.json (1)

8-12: Branch whitespace validation is properly handled in the config parser.

The schema correctly enforces minLength: 1 for the branch property. The config parser at src/config/tolgeerc.ts (lines 42-49) validates that whitespace-only strings are rejected by checking !rc.branch.trim().length before accepting the value, and then trims the branch accordingly.

src/commands/push.ts (2)

32-32: LGTM!

Import is correctly added for the new branch utility function.


225-225: LGTM!

Branch parameter is correctly included in the import request payload, consistent with the new branching feature.

src/options.ts (2)

36-36: LGTM!

The optional branch field is correctly added to BaseOptions, consistent with other optional fields in the interface.


59-62: LGTM!

The new PROJECT_BRANCH option follows the established patterns for other CLI options:

  • Uses consistent flag naming (-b, --branch)
  • Environment variable support via .env('TOLGEE_BRANCH')
  • Clear description indicating when to use the option
src/config/tolgeerc.ts (2)

1-12: LGTM!

Good modernization of imports:

  • Using node: prefixes for Node.js built-in modules is the recommended ESM convention
  • Type-only imports for CosmiconfigResult and Schema are appropriate since they're only used for type annotations

42-49: LGTM!

The branch validation logic is well-implemented:

  • Correctly handles undefined (skips validation)
  • Validates non-empty string requirement
  • Properly trims whitespace and stores the normalized value
  • Error message is consistent with the existing validation style

The defensive typeof rc.branch !== 'string' check provides good runtime safety for user-provided config values, which is appropriate given configs are loaded dynamically. Based on learnings, this runtime validation complements the JSON schema validation that runs afterwards.

@zomp zomp force-pushed the janmolnar/branching branch from 5fc1647 to fd4c053 Compare December 19, 2025 16:58
zomp added 6 commits December 29, 2025 12:28
There is `enable-pre-post-scripts` option which is set to true and is true by default, but it throws a warning, so I am removing it:
npm warn Unknown project config "enable-pre-post-scripts". This will stop working in the next major version of npm.
@zomp zomp force-pushed the janmolnar/branching branch from fd4c053 to 5a93411 Compare December 31, 2025 01:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd4c053 and 5a93411.

📒 Files selected for processing (17)
  • .npmrc
  • HACKING.md
  • schema.json
  • src/cli.ts
  • src/client/errorFromLoadable.ts
  • src/client/internal/schema.generated.ts
  • src/commands/pull.ts
  • src/commands/push.ts
  • src/commands/sync/compare.ts
  • src/config/tolgeerc.ts
  • src/options.ts
  • src/schema.d.ts
  • src/utils/branch.ts
  • test/__fixtures__/validTolgeeRc/withBranch.json
  • test/e2e/compare.test.ts
  • test/e2e/utils/api/common.ts
  • test/unit/config.test.ts
💤 Files with no reviewable changes (1)
  • .npmrc
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/commands/sync/compare.ts
  • src/cli.ts
  • src/options.ts
  • src/config/tolgeerc.ts
  • schema.json
  • src/utils/branch.ts
  • test/fixtures/validTolgeeRc/withBranch.json
  • src/commands/pull.ts
  • src/commands/push.ts
  • test/unit/config.test.ts
  • HACKING.md
  • src/schema.d.ts
🧰 Additional context used
🧬 Code graph analysis (2)
test/e2e/compare.test.ts (3)
test/e2e/utils/api/common.ts (4)
  • createProjectWithClient (49-94)
  • createBranch (100-126)
  • createKey (132-146)
  • deleteProject (39-43)
test/e2e/utils/api/project2.ts (1)
  • PROJECT_2 (6-15)
test/e2e/utils/run.ts (1)
  • run (106-112)
test/e2e/utils/api/common.ts (1)
src/client/TolgeeClient.ts (1)
  • TolgeeClient (34-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E Tests (ubuntu-latest, 20)
  • GitHub Check: E2E Tests (ubuntu-latest, 22)
  • GitHub Check: E2E Tests (ubuntu-latest, 18)
🔇 Additional comments (7)
src/client/errorFromLoadable.ts (1)

46-49: LGTM! Clean addition of 404 error handling.

The new case for HTTP 404 follows the established pattern and provides a clear, actionable error message. The inline comment helpfully contextualizes the scenario (missing branch), which aligns well with the PR's branching feature.

test/e2e/compare.test.ts (4)

6-7: LGTM!

The new imports are properly used in the branching test suite.


39-39: LGTM!

Good consistency improvement to match the PROJECT_2 constant being used.


71-72: LGTM!

The updated assertions with +/- prefixes improve clarity and align with the enhanced output format of the compare command.

Also applies to: 86-87, 111-116, 135-140


160-230: Well-structured branching test suite.

The tests comprehensively cover:

  • Keys existing in the feature branch
  • Keys not existing in the main branch
  • Error handling for non-existent branches

The setup and teardown logic is appropriate.

test/e2e/utils/api/common.ts (2)

56-58: LGTM!

Adding the filterCurrentUserOwner filter improves test reliability by ensuring the correct organization is selected.


128-146: LGTM!

Clean and type-safe implementation with appropriate defaults and option handling.

@zomp zomp force-pushed the janmolnar/branching branch from 5a93411 to d8da832 Compare December 31, 2025 02:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
test/e2e/compare.test.ts (1)

212-229: Consider asserting a more specific error code or message.

The test correctly verifies that referencing a non-existent branch fails. However, checking out.stderr + out.stdout is a bit broad. If the CLI has a consistent error output location, consider checking only that stream. Also, verifying the exit code is non-zero is good, but you might want to assert a specific exit code if the CLI uses distinct codes for different error types.

test/e2e/utils/api/common.ts (1)

96-125: Consider returning the created branch for potential future use.

The function works correctly for current test needs. However, returning the API response would allow callers to access the created branch ID if needed later.

🔎 Proposed enhancement
 export async function createBranch(
   client: TolgeeClient,
   name: string,
   options?: CreateBranchOptions
 ) {
   let originBranchId = options?.originBranchId;
   if (!originBranchId) {
     const branches = await client.GET('/v2/projects/{projectId}/branches', {
       params: {
         path: { projectId: client.getProjectId() },
       },
     });

     const origin = branches.data?._embedded?.branches?.find((b) => b.isDefault);
     originBranchId = origin?.id;
   }

   if (!originBranchId) {
     throw new Error('Default branch not found');
   }

-  await client.POST('/v2/projects/{projectId}/branches', {
+  return await client.POST('/v2/projects/{projectId}/branches', {
     params: { path: { projectId: client.getProjectId() } },
     body: { name, originBranchId },
   });
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a93411 and d8da832.

📒 Files selected for processing (4)
  • src/client/errorFromLoadable.ts
  • src/utils/branch.ts
  • test/e2e/compare.test.ts
  • test/e2e/utils/api/common.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/branch.ts
🧰 Additional context used
🧬 Code graph analysis (2)
test/e2e/utils/api/common.ts (2)
src/client/TolgeeClient.ts (1)
  • TolgeeClient (34-34)
src/client/internal/schema.generated.ts (1)
  • components (5440-9752)
test/e2e/compare.test.ts (2)
test/e2e/utils/api/common.ts (4)
  • createProjectWithClient (49-94)
  • createPak (159-165)
  • createBranch (100-125)
  • createKey (131-145)
test/e2e/utils/api/project2.ts (1)
  • PROJECT_2 (6-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E Tests (ubuntu-latest, 18)
  • GitHub Check: E2E Tests (ubuntu-latest, 20)
  • GitHub Check: E2E Tests (ubuntu-latest, 22)
🔇 Additional comments (10)
src/client/errorFromLoadable.ts (1)

46-48: LGTM! Clean addition of 404 error handling.

The implementation correctly handles HTTP 404 responses with a clear, actionable error message. The comment provides helpful context about branch-related scenarios, and the code follows the established pattern consistently.

test/e2e/compare.test.ts (6)

6-11: LGTM!

The imports for createBranch and createKey are correctly added and used in the new Branching test suite.


39-39: LGTM!

The describe label correctly reflects that this test suite uses PROJECT_2 data.


111-116: LGTM!

The updated expectations correctly reflect the new output format with + prefix for added keys and - prefix for removed keys.

Also applies to: 135-140


160-179: LGTM!

The Branching test setup is well-structured: creates project, PAK, feature branch, and adds keys to the branch. The afterEach cleanup ensures proper teardown.


181-198: LGTM!

Good test coverage - verifies that keys created on feature-branch are detected as in-sync when explicitly targeting that branch via --branch option.


200-210: LGTM!

Correctly validates that branch-specific keys don't appear in the main branch, resulting in an "out of sync" status with 2 new keys reported.

test/e2e/utils/api/common.ts (3)

56-58: LGTM!

The formatting change is minor and doesn't affect functionality.


127-145: LGTM!

The createKey helper is well-designed:

  • Uses Partial<Omit<...>> to make all fields optional except name
  • Correctly defaults isPlural to false as required by CreateKeyDto
  • Returns the API response for callers who need it

147-157: LGTM!

The expanded DEFAULT_SCOPES include the necessary permissions (keys.create, keys.edit, etc.) for the new branch and key creation tests.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/commands/tag.ts (1)

47-64: Don’t always send branch in query; omit it when undefined (avoids branch=undefined/empty serialization risk).

On Line 52, consider conditionally adding the query param (you already do this pattern in tests).

Proposed fix
     const loadable = await loading(
       `Tagging${appendBranch(opts.branch)}...`,
       opts.client.PUT('/v2/projects/{projectId}/tag-complex', {
         params: {
           path: { projectId: opts.client.getProjectId() },
-          query: { branch: opts.branch },
+          query: { ...(opts.branch ? { branch: opts.branch } : {}) },
         },
         body: {
           filterTag: opts.filterTag,
           filterTagNot: opts.filterNoTag,
           tagFiltered: opts.tag,
           tagOther: opts.tagOther,
           untagFiltered: opts.untag,
           untagOther: opts.untagOther,
           filterKeys: opts.filterExtracted ? extractedKeys : undefined,
           filterKeysNot: opts.filterNotExtracted ? extractedKeys : undefined,
         },
       })
     );
src/commands/sync/sync.ts (1)

187-197: Missing branch parameter in DELETE keys API call.

Line 189 includes branch in the user message, but lines 190-194 don't pass the branch query parameter to the DELETE endpoint. This is inconsistent with the create flow (lines 169-172) which correctly includes branch in the query.

This means keys will be deleted from the wrong branch (default) when opts.branch is specified.

🔧 Proposed fix
       const loadable = await loading(
         `Deleting unused keys${appendBranch(opts.branch)}...`,
         opts.client.DELETE('/v2/projects/{projectId}/keys', {
-          params: { path: { projectId: opts.client.getProjectId() } },
+          params: {
+            path: { projectId: opts.client.getProjectId() },
+            query: { branch: opts.branch },
+          },
           body: { ids },
         })
       );
🤖 Fix all issues with AI agents
In @src/commands/merge.ts:
- Around line 53-58: The buildMergeUrl function currently constructs frontend
URLs using opts.apiUrl which fails when API and frontend domains differ; update
the MergeOptions/BaseOptions types to include an optional frontendUrl and change
buildMergeUrl to prefer opts.frontendUrl (falling back to opts.apiUrl) when
building the URL, or alternatively add clear documentation on the limitation if
you choose not to update types; reference MergeOptions, BaseOptions, and
buildMergeUrl to locate where to add the new optional frontendUrl and use it
when composing the `/projects/${opts.projectId}/branches/merge/${mergeId}` URL.

In @test/e2e/pull.test.ts:
- Around line 435-451: The Branching test suite calls setupTemporaryFolder() but
never removes it; update the teardown to call and await removeTmpFolder() (or
removeTmpFolder in afterEach/afterAll alongside deleteProject) so TMP_FOLDER is
cleaned up; ensure removeTmpFolder is imported/available and placed in the same
afterEach block that currently calls deleteProject to guarantee the temp folder
is removed after each test.
🧹 Nitpick comments (3)
src/commands/tag.ts (1)

47-49: Loading message for “no branch” might be noisier than needed.

On Line 48, appendBranch(undefined) produces (none or default branch) (per src/utils/branch.ts). If you want the default UX to remain “Tagging…”, consider making appendBranch return '' when no branch, and using a separate helper where you explicitly want to mention default branch.

test/e2e/tags.test.ts (1)

258-307: Branching test: prefer asserting on a specific key instead of “exactly 1 key has tag” (less fixture-coupled).

On Lines 279-284, asserting .length === 1 will break if the fixture gains another extracted key. If the intent is “controller is tagged in branch only”, assert on that key name directly.

src/utils/branch.ts (1)

6-8: Clarify the message for the no-branch case.

The message " (none or default branch)" conflates two distinct states:

  • No branch specified (none)
  • Default branch is being used

This ambiguity could confuse users. Consider:

  • " (default branch)" if you can determine the default is being used
  • " (no branch specified)" if branch context is absent
  • Or split the logic to show the actual default branch name when available
💡 Suggested improvement
-export function appendBranch(branch?: string) {
-  return branch ? ` (branch "${branch}")` : ' (none or default branch)';
-}
+export function appendBranch(branch?: string) {
+  return branch ? ` (branch "${branch}")` : ' (default branch)';
+}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8da832 and 7a501ec.

📒 Files selected for processing (13)
  • src/cli.ts
  • src/client/internal/schema.generated.ts
  • src/commands/branch.ts
  • src/commands/merge.ts
  • src/commands/sync/sync.ts
  • src/commands/tag.ts
  • src/utils/branch.ts
  • test/e2e/branch.test.ts
  • test/e2e/merge.test.ts
  • test/e2e/pull.test.ts
  • test/e2e/sync.test.ts
  • test/e2e/tags.test.ts
  • test/e2e/utils/api/tags.ts
🧰 Additional context used
🧬 Code graph analysis (9)
src/commands/tag.ts (1)
src/utils/branch.ts (1)
  • appendBranch (6-8)
test/e2e/tags.test.ts (2)
test/e2e/utils/api/common.ts (1)
  • createBranch (100-125)
test/e2e/utils/api/tags.ts (1)
  • getTagsMap (42-56)
src/commands/merge.ts (4)
src/options.ts (1)
  • BaseOptions (32-42)
src/utils/logger.ts (4)
  • loading (125-155)
  • error (69-71)
  • info (42-44)
  • success (51-53)
src/utils/branch.ts (2)
  • fetchBranches (10-16)
  • listBranches (18-33)
src/client/TolgeeClient.ts (1)
  • handleLoadableError (22-26)
test/e2e/branch.test.ts (5)
src/client/TolgeeClient.ts (1)
  • TolgeeClient (34-34)
src/utils/branch.ts (1)
  • fetchBranches (10-16)
test/e2e/utils/api/common.ts (5)
  • createProjectWithClient (49-94)
  • createPak (159-165)
  • DEFAULT_SCOPES (147-157)
  • deleteProject (39-43)
  • createBranch (100-125)
test/e2e/utils/api/project1.ts (1)
  • PROJECT_1 (6-15)
test/e2e/utils/run.ts (1)
  • run (106-112)
test/e2e/utils/api/tags.ts (1)
src/client/TolgeeClient.ts (1)
  • TolgeeClient (34-34)
src/cli.ts (3)
src/options.ts (1)
  • PROJECT_BRANCH (59-62)
src/commands/branch.ts (1)
  • config (116-135)
src/commands/merge.ts (1)
  • config (171-175)
test/e2e/merge.test.ts (3)
test/e2e/utils/api/common.ts (6)
  • createProjectWithClient (49-94)
  • createPak (159-165)
  • DEFAULT_SCOPES (147-157)
  • deleteProject (39-43)
  • createBranch (100-125)
  • createKey (131-145)
test/e2e/utils/api/project1.ts (1)
  • PROJECT_1 (6-15)
test/e2e/utils/run.ts (1)
  • run (106-112)
src/commands/sync/sync.ts (3)
src/client/TolgeeClient.ts (1)
  • TolgeeClient (34-34)
src/utils/logger.ts (1)
  • loading (125-155)
src/utils/branch.ts (1)
  • appendBranch (6-8)
test/e2e/sync.test.ts (4)
test/e2e/utils/api/common.ts (5)
  • createProjectWithClient (49-94)
  • createPak (159-165)
  • createBranch (100-125)
  • deleteProject (39-43)
  • createKey (131-145)
test/e2e/utils/api/project2.ts (1)
  • PROJECT_2 (6-15)
test/e2e/utils/tmp.ts (1)
  • removeTmpFolder (35-40)
test/e2e/utils/run.ts (1)
  • run (106-112)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E Tests (ubuntu-latest, 18)
  • GitHub Check: E2E Tests (ubuntu-latest, 22)
  • GitHub Check: E2E Tests (ubuntu-latest, 20)
🔇 Additional comments (20)
test/e2e/utils/api/tags.ts (1)

42-48: Branch-scoped tag fetching looks correct; nice conditional query merge.

test/e2e/tags.test.ts (1)

53-65: Switch to --config usage is consistent and reads well.

Also applies to: 84-85, 96-110, 130-131, 142-151, 167-168, 177-190, 209-210, 221-232, 241-250

test/e2e/sync.test.ts (2)

13-20: Branch helpers import integration is straightforward.


290-392: Branching coverage is solid (branch-scoped keys + missing branch).

test/e2e/pull.test.ts (2)

13-19: Branch helpers import integration is straightforward.


452-495: Branch pull scenarios (include/exclude + missing branch) are well targeted.

src/cli.ts (1)

22-22: LGTM! Clean integration of branch functionality.

The branch option and commands are properly wired into the CLI:

  • PROJECT_BRANCH option correctly imported and registered with config default
  • BranchCommand and MergeCommand follow the established pattern for command registration
  • Global options are properly exposed for both commands

Also applies to: 40-41, 186-186, 216-221

src/utils/branch.ts (1)

10-16: LGTM! Branch utilities are well-structured.

Both fetchBranches and listBranches are cleanly implemented:

  • Proper error handling via handleLoadableError
  • Clear branch markers (default, protected, inactive, ongoing merge)
  • Good user feedback

Also applies to: 18-33

test/e2e/merge.test.ts (1)

16-24: LGTM! Comprehensive e2e test coverage.

The test suite effectively covers both merge scenarios:

  • Success case: Verifies clean merge completes with exit code 0 and correct output
  • Conflict case: Ensures conflicts are detected, reported, and prevent merge completion
  • Proper setup/teardown with project lifecycle management
  • Good assertions on both stdout/stderr and API state

Also applies to: 26-46, 48-74

src/commands/sync/sync.ts (1)

22-22: LGTM! Branch context properly integrated into sync flow.

The branch parameter is correctly threaded through:

  • Backup export with filterBranch
  • All-keys fetch with branch query param
  • Key creation/import with branch query param
  • User-facing messages with appendBranch suffix

Also applies to: 33-48, 96-104, 146-148, 167-175

src/commands/merge.ts (1)

18-29: LGTM! Well-structured merge implementation.

The merge command is cleanly implemented with:

  • Good validation: Mutual exclusivity check for branch argument vs option (lines 19-23)
  • Clear user feedback: Change rendering with symbols (+, ~, -, x) and language tags
  • Proper error handling: Guards against non-existent and default branches
  • Conflict detection: Robust logic with fallback from API count to client-side filtering (lines 137-142)
  • Complete workflow: Preview → refresh → check → apply with loading indicators at each step

Also applies to: 31-51, 60-169

src/commands/branch.ts (5)

1-13: LGTM! Clean imports and type definitions.

The imports are well-organized, and the BranchOptions type definition properly extends BaseOptions with the necessary branch-specific options.


15-32: LGTM! Proper validation of mutual exclusivity.

The function correctly enforces that the positional [branch] argument cannot be used together with --create or --delete options. Error messages are clear and guide users to the correct usage.


34-59: LGTM! Solid origin resolution with helpful error handling.

The function properly resolves the origin branch, falling back to the project's default branch when no origin is specified. The inclusion of listBranches() in error paths provides helpful feedback to users.


61-114: LGTM! Well-structured command handler with proper error handling.

The handler cleanly separates the list, create, and delete flows. Good use of:

  • Loading indicators for better UX
  • handleLoadableError for consistent error handling
  • Clear success messages
  • Branch existence validation before deletion

116-135: LGTM! Proper commander configuration with mutual exclusivity.

The command is well-configured with:

  • Clear description and argument definition
  • Proper use of conflicts() for mutually exclusive options
  • Optional positional argument that supports the list-by-default behavior

One thing to verify: Branch management operations require the project.edit scope (as seen in the test file). Ensure this requirement is documented in user-facing documentation or help text.

test/e2e/branch.test.ts (4)

1-26: LGTM! Well-organized test setup with clean helpers.

The test helpers (fetchBranches and findBranch) provide a clean abstraction for test assertions. While fetchBranches duplicates logic from src/utils/branch.ts, this is acceptable for test isolation—tests should verify behavior via direct API calls rather than relying on implementation details.


28-82: LGTM! Comprehensive success path testing.

The tests properly cover:

  • Project setup with correct scopes (project.edit required for branch operations)
  • Clean teardown to avoid test pollution
  • Listing branches with expected output format
  • Creating branches via positional argument
  • Creating branches with explicit origin and verifying the fork relationship

Each test validates both CLI output and actual API state, ensuring end-to-end correctness.


84-138: LGTM! Thorough error handling validation.

The tests properly verify error scenarios:

  • Branch deletion workflow with API verification
  • Missing origin branch error with helpful error messages
  • Missing target branch error with guidance
  • All error cases check for non-zero exit codes and include branch listings to aid users

The pattern of combining stderr + stdout for output assertions is a good practice for CLI testing.


140-170: LGTM! Complete validation of mutual exclusivity constraints.

These tests verify that the CLI properly enforces mutual exclusivity between the positional [branch] argument and the --create/--delete options. The error messages are validated to ensure they provide clear guidance on correct usage.

@zomp zomp force-pushed the janmolnar/branching branch from 7a501ec to e27827b Compare January 9, 2026 15:20
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/commands/merge.ts:
- Around line 53-58: The buildMergeUrl function can produce a URL pointing at
opts.apiUrl which may be the wrong host for frontend links; update MergeOptions
to accept an explicit webUrl (e.g., add webUrl?: string) and change
buildMergeUrl to use opts.webUrl when present, falling back to opts.apiUrl only
if webUrl is undefined; also update any help text or docs to mention the new
webUrl option so users know to configure a separate frontend URL when API and
web hosts differ.
🧹 Nitpick comments (1)
src/commands/merge.ts (1)

125-148: LGTM with a note on complexity.

The change display and conflict detection logic is correct. The conflict count resolution (lines 137-141) uses a reasonable fallback strategy—prioritizing the server-provided count, then falling back to client-side counting. The logic is sound, though somewhat complex.

Optional: Extract conflict detection for clarity

Consider extracting the conflict count logic into a helper function to improve readability:

function getUnresolvedConflictCount(
  refreshData: any,
  changes: MergeChange[]
): number {
  return (
    refreshData?.keyUnresolvedConflictsCount ??
    changes.filter(
      (change) => change.type === 'CONFLICT' && !change.resolution
    ).length
  );
}

Then use: if (getUnresolvedConflictCount(refreshLoadable.data, changes) > 0) { ... }

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a501ec and e27827b.

📒 Files selected for processing (5)
  • src/cli.ts
  • src/commands/branch.ts
  • src/commands/merge.ts
  • src/utils/branch.ts
  • test/e2e/merge.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils/branch.ts
  • test/e2e/merge.test.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/cli.ts (3)
src/options.ts (1)
  • PROJECT_BRANCH (59-62)
src/commands/branch.ts (1)
  • config (116-135)
src/commands/merge.ts (1)
  • config (171-175)
src/commands/merge.ts (4)
src/options.ts (1)
  • BaseOptions (32-42)
src/utils/logger.ts (5)
  • exitWithError (103-116)
  • loading (125-155)
  • error (69-71)
  • info (42-44)
  • success (51-53)
src/utils/branch.ts (2)
  • fetchBranches (10-16)
  • listBranches (18-33)
src/client/TolgeeClient.ts (1)
  • handleLoadableError (22-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E Tests (ubuntu-latest, 20)
  • GitHub Check: E2E Tests (ubuntu-latest, 18)
  • GitHub Check: E2E Tests (ubuntu-latest, 22)
🔇 Additional comments (16)
src/cli.ts (4)

22-22: LGTM!

The import of PROJECT_BRANCH is correctly placed and follows the existing pattern for importing options.


40-41: LGTM!

The command imports are correctly placed and follow the existing pattern for importing command modules.


186-186: LGTM!

The PROJECT_BRANCH option is correctly wired with a default from the configuration, following the same pattern as other optional settings.


216-221: LGTM!

The command registrations for BranchCommand and MergeCommand correctly follow the established pattern used for other commands in the CLI.

src/commands/merge.ts (7)

1-16: LGTM!

Imports and type definitions are clean and follow the existing patterns in the codebase.


18-29: LGTM!

The branch name resolution logic correctly enforces mutual exclusivity and provides clear error messages.


31-51: LGTM!

The rendering functions provide clear, intuitive representations of merge changes with appropriate fallback handling.


60-80: LGTM!

The initial validation logic is thorough, with good error messages and helpful feedback (branch listing) when validation fails.


82-123: LGTM!

The merge preparation and change fetching logic follows the established API call patterns with appropriate error handling and user feedback.


150-169: LGTM!

The merge application and success reporting follow the established patterns and provide clear feedback to the user.


171-175: LGTM!

The command export follows the standard pattern established in the codebase.

src/commands/branch.ts (5)

1-13: LGTM!

Imports and type definitions are clean and appropriate for the command's functionality.


15-32: LGTM!

The target name resolution correctly enforces mutual exclusivity constraints with clear error messages. The comment appropriately acknowledges Commander's built-in conflict handling.


34-59: LGTM!

The origin branch resolution logic is solid, with appropriate validation and a sensible fallback to the default branch.


61-114: LGTM!

The branch handler provides a clean, well-structured implementation with proper validation, error handling, and user feedback for both create and delete operations.


116-135: LGTM!

The command definition is well-structured, making good use of Commander's built-in features for option conflicts and providing clear descriptions.

@zomp zomp requested a review from dkrizan January 9, 2026 16:33
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @src/commands/sync/sync.ts:
- Around line 189-194: The DELETE call is placing the branch query at the top
level of the request options instead of inside params, so the branch is ignored;
update the opts.client.DELETE invocation to nest the branch under params (same
shape as the GET/POST requests) — i.e. keep params: { path: { projectId:
opts.client.getProjectId() }, ...(!!opts.branch && { query: { branch:
opts.branch } }) } alongside body: { ids } in the
opts.client.DELETE('/v2/projects/{projectId}/keys', ...) call (referencing
opts.client.DELETE, opts.branch and ids).
🧹 Nitpick comments (1)
src/commands/sync/compare.ts (1)

29-37: Nice integration of branch support with good UX!

The loading wrapper and branch-aware message enhance the user experience. The conditional branch parameter works correctly.

The spread syntax ...(!!opts.branch && { query: { branch: opts.branch } }) is functional but could be more readable. Consider a more conventional alternative:

♻️ Optional readability improvement
     const loadable = await loading(
       `Fetching Tolgee keys${appendBranch(opts.branch)}...`,
       opts.client.GET('/v2/projects/{projectId}/all-keys', {
         params: {
           path: { projectId: opts.client.getProjectId() },
-          ...(!!opts.branch && { query: { branch: opts.branch } }),
+          ...(opts.branch ? { query: { branch: opts.branch } } : {}),
         },
       })
     );

This makes the conditional logic more explicit without relying on spreading falsy values.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e27827b and 4cfed9d.

📒 Files selected for processing (3)
  • src/commands/sync/compare.ts
  • src/commands/sync/sync.ts
  • src/commands/tag.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/tag.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/commands/sync/compare.ts (2)
src/utils/logger.ts (1)
  • loading (125-155)
src/utils/branch.ts (1)
  • appendBranch (6-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: E2E Tests (ubuntu-latest, 22)
  • GitHub Check: E2E Tests (ubuntu-latest, 20)
  • GitHub Check: E2E Tests (ubuntu-latest, 18)
🔇 Additional comments (6)
src/commands/sync/compare.ts (1)

14-14: LGTM!

The import of appendBranch is clean and supports the branch-aware messaging enhancement.

src/commands/sync/sync.ts (5)

22-22: LGTM!

The import of appendBranch utility is appropriate for adding branch context to user-facing messages.


33-40: LGTM!

The backup function signature correctly extends to include an optional branch parameter, and the parameter is properly passed to the export call as filterBranch.


96-104: LGTM!

The GET request correctly includes branch context in both the loading message and query parameters. The conditional spreading ensures the query parameter is only added when a branch is provided.


146-147: LGTM!

The backup invocation correctly passes the branch parameter and includes branch context in the loading message.


167-174: LGTM!

The POST request correctly includes branch context in both the loading message and query parameters, with proper structure matching the GET request pattern.

@zomp zomp marked this pull request as ready for review January 9, 2026 16:35
@zomp zomp force-pushed the janmolnar/branching branch from 4cfed9d to 606f9fc Compare January 9, 2026 16:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/sync/sync.ts (1)

188-194: Branch parameter missing from DELETE operation.

The loading message indicates branch-aware deletion via appendBranch(opts.branch), but the DELETE API call doesn't include the branch as a query parameter. This is inconsistent with the GET (lines 96-103) and POST (lines 166-174) operations, which both conditionally pass the branch.

This inconsistency could lead to keys being deleted from the wrong branch or unexpected API behavior.

🔧 Proposed fix
 const loadable = await loading(
   `Deleting unused keys${appendBranch(opts.branch)}...`,
   opts.client.DELETE('/v2/projects/{projectId}/keys', {
-    params: { path: { projectId: opts.client.getProjectId() } },
+    params: {
+      path: { projectId: opts.client.getProjectId() },
+      ...(!!opts.branch && { query: { branch: opts.branch } }),
+    },
     body: { ids },
   })
 );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4cfed9d and 606f9fc.

📒 Files selected for processing (3)
  • src/commands/sync/compare.ts
  • src/commands/sync/sync.ts
  • src/commands/tag.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/commands/tag.ts (1)
src/utils/branch.ts (1)
  • appendBranch (6-8)
src/commands/sync/compare.ts (2)
src/utils/logger.ts (1)
  • loading (125-155)
src/utils/branch.ts (1)
  • appendBranch (6-8)
src/commands/sync/sync.ts (3)
src/client/TolgeeClient.ts (1)
  • TolgeeClient (34-34)
src/utils/logger.ts (1)
  • loading (125-155)
src/utils/branch.ts (1)
  • appendBranch (6-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: E2E Tests (ubuntu-latest, 18)
  • GitHub Check: Unit Tests (windows-latest, 22)
  • GitHub Check: E2E Tests (ubuntu-latest, 22)
  • GitHub Check: E2E Tests (ubuntu-latest, 20)
🔇 Additional comments (6)
src/commands/tag.ts (1)

47-65: LGTM! Branch support correctly integrated.

The branch parameter is properly threaded through the tagging operation:

  • Loading message reflects the branch context via appendBranch
  • API call conditionally includes branch as a query parameter
  • Implementation is clean and consistent
src/commands/sync/compare.ts (1)

29-37: LGTM! Branch awareness correctly implemented.

The compare flow now supports branch-specific key fetching with appropriate user feedback.

src/commands/sync/sync.ts (4)

33-48: LGTM! Backup function correctly extended for branch support.

The function signature change is minimal and the branch is properly passed to the export operation via filterBranch.


96-104: LGTM! Branch support correctly added to key fetching.


145-148: LGTM! Backup invocation correctly passes branch.


166-175: LGTM! Key creation correctly includes branch context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants